V4 to V5
Standalone Components Migration
- Open a terminal into your angular root for each of your angular front project
- Run command
ng g @angular/core:standalone
- Select Convert standalone
- Choose current root path of your project and validate
- Run command
ng g @angular/core:standalone
again- Select Remove modules
- Choose current root path of your project and validate
- Run
npm run clean
command - Commit the changes
Angular 19 Migration
Editor's migration guides
Angular CDK (v16 -> v17)
- Open a terminal into your angular root project
- Run command
ng update @angular/cdk@17
, you'll have the following automatic migrations :- From @angular/cdk
- Updates the Angular CDK to v17
- From @angular/cdk
- Commit the changes
Angular Core (v17 -> v18)
- Run command
ng update @angular/core@18 @angular/cli@18 @angular-eslint/schematics@18
, you'll have the following automatic migrations :- From @angular-eslint/schematics
- Updates @angular-eslint to v18.2
- From @angular/cli
- Migrate application projects to the new build system : you'll have to accept the migration action
use-application-builder
- Migrate application projects to the new build system : you'll have to accept the migration action
- From @angular/core
- Updates two-way bindings that have an invalid expression to use the longform expression instead.
- Replace deprecated HTTP related modules with provider functions
- Updates calls to afterRender with an explicit phase to the new API
- From @angular-eslint/schematics
- Commit the changes
Angular Packages (v17 -> v18)
- Run command
ng update @angular/cdk@18 @ngrx/store@18 @ngrx/store-devtools@18 keycloak-angular@16 primeng@18
, you'll have the following automatic migrations :- From @angular/cdk
- Updates the Angular CDK to v18
- From @ngrx/effects
- As of NgRx v18, the
concatLatestFrom
import has been removed from@ngrx/effects
in favor of the@ngrx/operators
package
- As of NgRx v18, the
- From @ngrx/store
- As of NgRx v18, the
TypedAction
has been removed in favor ofAction
- As of NgRx v18, the
- From @angular/cdk
- Commit the changes
Angular Core + Packages (v18 -> v19)
- Run command
ng update @angular/core@19 @angular/cli@19 @angular/build@19 @angular/animations@19 @angular-eslint/schematics@19 @angular/cdk@19 @ngrx/store@19 @ngrx/store-devtools@19 keycloak-angular@19 primeng@19
, you'll have the following automatic migrations :- From @angular/cli
- Update '@angular/ssr' import paths to use the new '/node' entry point when 'CommonEngine' is detected
- Update the workspace configuration by replacing deprecated options in 'angular.json' for compatibility with the latest Angular CLI changes
- Migrate application projects to the new build system : you'll have to ignore the migration action
use-application-builder
- From @angular/cdk
- Updates the Angular CDK to v19
- From @angular/core
- Updates non-standalone Directives, Component and Pipes to 'standalone:false' and removes 'standalone:true' from those who are standalone
- Updates ExperimentalPendingTasks to PendingTasks
- Replaces
APP_INITIALIZER
,ENVIRONMENT_INITIALIZER
&PLATFORM_INITIALIZER
respectively withprovideAppInitializer
,provideEnvironmentInitializer
&providePlatformInitializer
: you'll have to accept the migration actionprovide-initialize
- From @ngrx/effects
- As of NgRx v18, the
concatLatestFrom
import has been removed from@ngrx/effects
in favor of the@ngrx/operators
package
- As of NgRx v18, the
- From @ngrx/store
- As of NgRx v18, the
TypedAction
has been removed in favor ofAction
- As of NgRx v18, the
- From @angular/cli
- Commit the changes
BIA Framework Migration
- Delete from your Angular projects all package-lock.json and node_modules folder
- Use the BIAToolKit to migrate the project :
- Run it automatically by clicking on Migrate button
or - Execute each step manually until step 3 - Apply Diff
- Run it automatically by clicking on Migrate button
- Mind to check the output logs to check any errors or missing deleted files
- Manage the conflicts (two solutions) :
- Merging rejected files
- Execute step 4 - Merge Rejected (already executed with automatic migration)
- Search
<<<<<
in all files - Resolve the conflicts
- Analyzing rejected files - MANUAL MIGRATION ONLY
- Analyze all the
.rej
files (search "diff a/" in VS code) - Apply manually the changes into your files
- Analyze all the
tipUse the conflict resolution chapter to help you
- Merging rejected files
- For each Angular project, launch the npm install and npm audit fix command
- Download the migration script and the standalone catch up script (right click -> Save link as) into the same directory
- Rename the downloaded scripts to remove the hash of the name (remove all after and including the last -)
- Change source path of the migration script to target your project root and your Angular project
- Run it for each of your Angular project (change the Angular source path each time)
- Apply other manual steps for Front (for each Angular project) and Back
- Resolve missing and obsolete usings in back-end with BIAToolKit (step 6 - Resolve Usings)
- Resolve building issues into your Angular projects and back end
- If all is ok, you can remove the
.rej
files. During the process they can be useful to resolve build problems - Execute the database migration instructions
- For each Angular project, launch the
npm run clean
command - Clean back-end solution
- Customize your application logo (doc)
Conflict resolution
You must pay attention to the conflict resoltuon of the following files.
TranslationModelBuilder.cs
- Call the base method into
CreateModel
,CreateLanguageModel
,CreateRoleTranslationModel
,CreateNotificationTypeTranslationModel
- Keep only one instruction of each
HasData
method
You must have this kind of result at the end :
TranslationModelBuilder.cs
/// <summary>
/// Class used to update the model builder for notification domain.
/// </summary>
public class TranslationModelBuilder : BaseTranslationModelBuilder
{
/// <summary>
/// Create the user model.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
public override void CreateModel(ModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
// Add here the project specific translation model creation.
Debug.Assert(modelBuilder != null, "Line to avoid warning empty method");
}
/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateLanguageModel(ModelBuilder modelBuilder)
{
base.CreateLanguageModel(modelBuilder);
modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.English, Code = "EN", Name = "English" });
modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.French, Code = "FR", Name = "Français" });
modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.Spanish, Code = "ES", Name = "Española" });
// Add here your own Languages
}
/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateRoleTranslationModel(ModelBuilder modelBuilder)
{
base.CreateRoleTranslationModel(modelBuilder);
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.Admin, LanguageId = LanguageId.French, Id = 1000101, Label = "Administrateur" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.Admin, LanguageId = LanguageId.Spanish, Id = 1000102, Label = "Administrador" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackAdmin, LanguageId = LanguageId.French, Id = 1000201, Label = "Administrateur des tâches en arrière-plan" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackAdmin, LanguageId = LanguageId.Spanish, Id = 1000202, Label = "Administrador de tareas en segundo plano" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackReadOnly, LanguageId = LanguageId.French, Id = 1000301, Label = "Visualisation des tâches en arrière-plan" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackReadOnly, LanguageId = LanguageId.Spanish, Id = 1000302, Label = "Visualización de tareas en segundo plano" });
// Add here your own RoleTranslations
}
/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateNotificationTypeTranslationModel(ModelBuilder modelBuilder)
{
base.CreateNotificationTypeTranslationModel(modelBuilder);
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Task, LanguageId = LanguageId.French, Id = 101, Label = "Tâche" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Task, LanguageId = LanguageId.Spanish, Id = 102, Label = "Tarea" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Info, LanguageId = LanguageId.French, Id = 201, Label = "Information" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Info, LanguageId = LanguageId.Spanish, Id = 202, Label = "Información" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Success, LanguageId = LanguageId.French, Id = 301, Label = "Succès" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Success, LanguageId = LanguageId.Spanish, Id = 302, Label = "Éxito" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Warning, LanguageId = LanguageId.French, Id = 401, Label = "Avertissement" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Warning, LanguageId = LanguageId.Spanish, Id = 402, Label = "Advertencia" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Error, LanguageId = LanguageId.French, Id = 501, Label = "Erreur" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Error, LanguageId = LanguageId.Spanish, Id = 502, Label = "Culpa" });
// Add here your own NotificationTypeTranslations
}
}
UserModelBuilder.cs
- Call the base method into
CreateUserModel
,CreateTeamTypeModel
,CreateRoleModel
,CreateTeamTypeRoleModel
- Keep only one instruction of each
HasData
method
You must have this kind of result at the end :
UserModelBuilder.cs
/// <summary>
/// Class used to update the model builder for user domain.
/// </summary>
public class UserModelBuilder : BaseUserModelBuilder
{
/// <summary>
/// Create the model for users.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateUserModel(ModelBuilder modelBuilder)
{
base.CreateUserModel(modelBuilder);
modelBuilder.Entity<User>(entity =>
{
entity.Property(u => u.Email).HasMaxLength(256);
});
}
/// <summary>
/// Create the model for teams.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateTeamTypeModel(ModelBuilder modelBuilder)
{
base.CreateTeamTypeModel(modelBuilder);
modelBuilder.Entity<TeamType>().HasData(new TeamType { Id = (int)TeamTypeId.Site, Name = "Site" });
// BIAToolKit - Begin TeamTypeModelBuilder
// BIAToolKit - End TeamTypeModelBuilder
}
/// <summary>
/// Create the model for roles.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateRoleModel(ModelBuilder modelBuilder)
{
base.CreateRoleModel(modelBuilder);
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.Admin, Code = "Admin", Label = "Administrator" });
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.BackAdmin, Code = "Back_Admin", Label = "Background task administrator" });
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.BackReadOnly, Code = "Back_Read_Only", Label = "Visualization of background tasks" });
// Add here your own Roles
// BIAToolKit - Begin RoleModelBuilder
// BIAToolKit - End RoleModelBuilder
}
/// <summary>
/// Create the model for member roles.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateTeamTypeRoleModel(ModelBuilder modelBuilder)
{
base.CreateTeamTypeRoleModel(modelBuilder);
modelBuilder.Entity<Role>()
.HasMany(p => p.TeamTypes)
.WithMany(r => r.Roles)
.UsingEntity(rt =>
{
rt.ToTable("RoleTeamTypes");
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.Admin });
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.BackAdmin });
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.BackReadOnly });
// Add here your own RoleTeamTypes
// BIAToolKit - Begin TeamTypeRoleModelBuilder
// BIAToolKit - End TeamTypeRoleModelBuilder
});
}
}
Front Manual Steps
- Replace
HttpClientModule
import byprovideHttpClient(withInterceptorsFromDi())
into the providers :
Before
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule
]
})
After
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
@NgModule({
providers: [
provideHttpClient(withInterceptorsFromDi())
]
})
- Replace all
@Output
properties with native event names such ascancel
,click
by another names (ex:cancelled
,clicked
) - Remove
Time
usage from your code - Adapt the usage of
<p-tabs>
migrated from<p-tabView>
:- Add
<p-tablist>
inside and under<p-tabs>
- For each existing
<p-tabpanel>
, add a<p-tab>
inside<p-tablist>
with an incremental numeric propertyvalue
, with the inner content of previousheader
property of the<p-tabpanel>
or of the<ng-template pTemplate="header">
content - Replace for each existing
<p-tabpanel>
propertyheader
byvalue
with corresponding value of the added<p-tab>
- Add into the
<p-tabs>
a propertyvalue
with the same identifier as your target tab to set as active by default
- Add
Before
<p-tabView>
<p-tabPanel *ngFor="let tab of tabs" header="tab.title">
{{ tab.content }}
</p-tabPanel>
</p-tabView>
<p-tabView>
<p-tabPanel *ngFor="let tab of tabs">
<ng-template pTemplate="header">
{{ tab.title }}
</ng-template>
{{ tab.content }}
</p-tabPanel>
</p-tabView>
After
<p-tabs [value]="0">
<p-tablist>
@for (tab of tabs; track $index) {
<p-tab [value]="$index">
{{ tab.title }}
</p-tab>
}
</p-tablist>
@for (tab of tabs; track $index) {
<p-tabpanel [value]="$index">
{{ tab.content }}
</p-tabpanel>
}
</p-tabs>
tip
Check the Tabs PrimeNG documentation to adapt the input/output bindings
- Fix the automatic replacements of
<p-floatlabel>
to the correct HTML tag if any - Fix the automatic replacements of
<p-fluid>
to the correct HTML tag if any - For each
<p-checkbox>
with label, add intodiv
parent container the classesflex items-center
Before
<div>
<p-checkbox></p-checkbox>
<label>Something</label>
</div>
After
<div class="flex items-center">
<p-checkbox></p-checkbox>
<label>Something</label>
</div>
- The eslint plugin
eqeqeq
(forcing usage of === instead of ==) should be activated after migration. You can keep it activated and fix all lint errors or you can deactivate it in the .eslintrc file of your project by changing every line where eqeqeq appears by :
"eqeqeq": "off"
- Moving advanced filters to the right :
- Move advanced filter html after table in index.component
- Move from table-header to table-controller html :
[showBtnFilter]="true"
[showFilter]="showAdvancedFilter"
[hasFilter]="hasAdvancedFilter"
(openFilter)="onOpenFilter()"
- Replace into your .scss files the usage of
@import
by@use
and add the import file name as suffix of the concerned properties
Before
@import '../theme'
button {
color: $button-color /* From theme */
}
After
@use '../theme'
button {
color: theme.$button-color
}
- It is no longer needed to use local assets as backgrounds for ColorPicker of PrimeNG
- Change references of
AuthInfo.additionalInfo.userInfo.id
toAuthInfo.decryptedToken.id
(If you were usingconst additionalInfo = this.authService.getAdditionalInfos();
to retrieve this value, use insteadconst decryptedToken = this.authService.getDecryptedToken()
) - Change references of
AuthInfo.additionalInfo.userInfo.login
toAuthInfo.decryptedToken.identityKey
(Same as above, use insteadconst decryptedToken = this.authService.getDecryptedToken()
) - Replace in HTML templates of components that inherits from
CrudItemFormComponent
the reference of outputcancel
bycancelled
<p-badge [value]="property">
: property must not be undefined or null- If previously using
<p-card>
with a<p-header>
and/or<p-footer>
, change theses tags by<ng-template #header>
or<ng-template #footer>
. You can then delete the importHeader
and/orFooter
from your TS component.
Before
<p-card>
<p-header></p-header>
<p-footer></p-footer>
</p-card>
After
<p-card>
<ng-template #header></ng-template>
<ng-template #footer></ng-template>
</p-card>
- Class
.p-fluid
has disappeared from primeng. Search for all references to class p-fluid in your html files and use instead the new primeng html element<p-fluid>
. Adapt your style : for example, if replaced html element was a div, the default display: block from the div might be lost when changing it to p-fluid html element. - Class
.p-float-label
has disappeared from primeng. If you have custom css using these classes, you will have to replace it for it to work again. Search in all your css files references to classes.p-float-label
and fix the style in found pages. - The constructor of
BiaTableControllerComponent
has changed. A new parameterRenderer2
has been added. If you have a component inheriting fromBiaTableControllerComponent
, you need to add it as shown in this example:
export class CustomTableControllerComponent extends BiaTableControllerComponent {
constructor(
public translateService: TranslateService,
protected renderer: Renderer2
) {
super(translateService, renderer);
}
}
- Remove into all your feature's modules the imports of your view components
Before
@NgModule({
imports: [
RouterModule.forChild(ROUTES),
StoreModule.forFeature(
myFeatureCRUDConfiguration.storeKey,
FeatureMyFeaturesStore.reducers
),
EffectsModule.forFeature([MyFeaturesEffects]),
SomeOptionModule,
// Your previous views imports
MyFeatureItemComponent,
MyFeaturesIndexComponent,
MyFeatureFormComponent,
MyFeatureNewComponent,
MyFeatureEditComponent,
MyFeatureTableComponent,
MyFeatureImportComponent,
],
})
export class MyFeatureModule {}
After
@NgModule({
imports: [
RouterModule.forChild(ROUTES),
StoreModule.forFeature(
myFeatureCRUDConfiguration.storeKey,
FeatureMyFeaturesStore.reducers
),
EffectsModule.forFeature([MyFeaturesEffects]),
SomeOptionModule,
],
})
export class MyFeatureModule {}
Back Manual Steps
Entities
- Into your Mappers, be careful on
DtoToEntity
function : the initialization of entity is mandatory else you will have crash during the creation of an item:- Search
public override void DtoToEntity
- Verify that there is one of these lines in the first row of the function:
base.DtoToEntity(dto, ref entity);
entity ??= new ...
entity = new TEntity ...
- If not add
base.DtoToEntity(dto, ref entity);
at the beginning.
- Search
- Build the solution, search and resolve all CS0108 warnings from your entities : remove all properties that hides inherited properties from
BaseEntityXXX
orBaseDtoXXX
classes
User
- Remove from
Infrastructure.Data.ModelBuilders.UserModelBuilder
class intoCreateUserModel
method all entities configuration that refers to properties included already intoBaseEntityUser
- Changes declarations of
UserOptionMapper
by providing a genericBaseEntityUser
type. Example :UserOptionMapper<User>
- If you have any custom method into
UserSpecification
used, handle reference to your project instead of BIA.Net.Core - Apply changes into
Domain.User.Mappers.UserMapper
- Into
ExpressionCollection
, modify the HeaderName struct reference to HeaderNameExtended for all of your properties and remove all mapping for properties included into HeaderName struct - Remove from
EntityToDto
the mapping of properties already included intoBaseEntityUser
class - Do the same for
DtoToEntity
- Rewrite into
DtoToCellMapping
the previous content of the methodDtoToRecord
for each properties of your customUser
class
- Into
Before
public override Func<UserDto, object[]> DtoToRecord(List<string> headerNames = null)
{
return x =>
{
List<object> records = new List<object>();
if (headerNames?.Any() == true)
{
foreach (string headerName in headerNames)
{
if (string.Equals(headerName, HeaderName.MyProperty, StringComparison.OrdinalIgnoreCase))
{
records.Add(CSVString(x.MyProperty));
}
// ...
}
}
return records.ToArray();
};
}
After
public override Dictionary<string, Func<string>> DtoToCellMapping(UserDto dto)
{
return new Dictionary<string, Func<string>>(base.DtoToCellMapping(dto))
{
{ HeaderNameExtended.MyProperty, () => CSVString(dto.MyProperty) },
// ...
};
}
Database Migration
- Add a new migration MigrationBiaFrameworkV5
- Edit the generated migration :
- Search for the following code into
Up()
migrationBuilder.DropColumn(
name: "IsDefault",
table: "Members");- Add before the following code :
migrationBuilder.Sql(@"
INSERT INTO UserDefaultTeam (UserId, TeamId)
SELECT UserId, TeamId
FROM Members
WHERE IsDefault = 1;
");warningEnsure to have this previous code after the
CreateTable
andCreateIndex
instruction forUserDefaultTeam
table- Search for the following code into
Up()
migrationBuilder.AddColumn<string>(
name: "Discriminator",
table: "Users",
type: "nvarchar(21)",
maxLength: 21,
nullable: false,
defaultValue: "");- Change the
defaultValue
toUser
- Search for the following code into
Down()
migrationBuilder.AddColumn<bool>(
name: "IsDefault",
table: "Members",
type: "bit",
nullable: false,
defaultValue: false);- Add after the following code :
migrationBuilder.Sql(@"
INSERT INTO Members (UserId, TeamId, IsDefault)
SELECT UserId, TeamId, 1
FROM UserDefaultTeam;
");warningEnsure to have this previous code before the
DropTable
instruction forUserDefaultTeam
table - Search for the following code into
- Update database